#!/bin/bash
#==============================================================#
# File      :   bootstrap
# Desc      :   setup local repo & install boot utils
# Ctime     :   2022-10-16
# Mtime     :   2023-02-26
# Path      :   bootstrap
# Author    :   Ruohang Feng (rh@vonng.com)
# License   :   AGPLv3
#==============================================================#
VERSION=v2.0.0


#--------------------------------------------------------------#
# Usage
#--------------------------------------------------------------#
#  boostrap [-p <path>] [-y|--yes] [-n|--no]
#
#  ./boostrap
#     [-r|--region <region]   [default,china,europe]
#     [-p|--path <path>]      specify another offline pkg path
#     [-y|--yes]              download offline pkg without asking
#     [-n|--no]               do not download offline pkg.tgz
#
# if neither `-y` or `-n` is specified, Pigsty will ask for
# downloading `pkg.tgz` from the Internet during `bootstrap`
#--------------------------------------------------------------#
# args
#  -r  --region <region> : mirror region to use (default|china|europe)
#  -p  --path   <path>   : offline packages path, /tmp/pkg.tgz by default
#  -y  --yes             : download pkg.tgz from internet, ask by default
#  -n  --no              : do not download pkg.tgz from internet
#--------------------------------------------------------------#


#--------------------------------------------------------------#
# Logic
#--------------------------------------------------------------#
# This script make sure two things:
#    1. ansible is installed
#    2. local repo is prepared
# It perform following tasks:
# 1. check preconditions
# 2. check local repo exists ?
#    Y -> create /etc/yum.repos.d/pigsty-local.repo
#    N -> Download from Internet? Y -> Download from Github / CDN and add local repo file
#                                 N -> Add basic os upstream repo manually ?
#                                         Y -> add according to region / releasever
#                                         N -> leave it to user
#  after step 2, we have yum repo available. Local > Download > Upstream > User Provide
#
# 3. install boot utils from available repo
#     nginx,wget,sshpass,createrepo_c,yum-utils
#     dnf-utils,modulemd-tools,python3-jmespath (el8/9)
#
# 4. Check ansible availability.
#--------------------------------------------------------------#


#----------------------------------------------#
# Param
#----------------------------------------------#
REGION=""               # which mirror to use? (default|china|europe)
DOWNLOAD_PKG=ask        # download pkg.tgz ? (ask|yes|no, ask by default)
PKG_PATH=/tmp/pkg.tgz   # which pkg to be used ? (/tmp/pkg.tgz by default)
PROG_NAME="$(basename $0)"
PIGSTY_HOME="$(cd $(dirname $0) && pwd)"
REPO_NAME=pigsty
NGINX_HOME=/www
REPO_DIR=${NGINX_HOME}/${REPO_NAME}
OS_VERSION=$(rpm -q --qf "%{VERSION}" $(rpm -q --whatprovides redhat-release) | grep -o '^[^.]\+')
ARCH=$(uname -m)
BASEURL="https://github.com/Vonng/pigsty/releases/download/${VERSION}"
SRC_FILENAME="pigsty-${VERSION}.tgz"
PKG_FILENAME="pigsty-pkg-${VERSION}.el${OS_VERSION}.x86_64.tgz"
DEFAULT_SRC_URL="${BASEURL}/${SRC_FILENAME}"
DEFAULT_PKG_URL="${BASEURL}/${PKG_FILENAME}"


#--------------------------------------------------------------#
# Utils
#--------------------------------------------------------------#
__CN='\033[0m';__CB='\033[0;30m';__CR='\033[0;31m';__CG='\033[0;32m';
__CY='\033[0;33m';__CB='\033[0;34m';__CM='\033[0;35m';__CC='\033[0;36m';__CW='\033[0;37m';
function log_info() {  printf "[${__CG} OK ${__CN}] ${__CG}$*${__CN}\n";   }
function log_warn() {  printf "[${__CY}WARN${__CN}] ${__CY}$*${__CN}\n";   }
function log_error() { printf "[${__CR}FAIL${__CN}] ${__CR}$*${__CN}\n";   }
function log_debug() { printf "[${__CB}HINT${__CN}] ${__CB}$*${__CN}\n"; }
function log_input() { printf "[${__CM} IN ${__CN}] ${__CM}$*\n=> ${__CN}"; }
function log_hint()  { printf "${__CB}$*${__CN}"; }
ipv4_regexp='(([0-9]|[0-9]{2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[0-9]{2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])'


#----------------------------------------------#
# region
#----------------------------------------------#
# return 0 if behind gfw (inside mainland china), otherwise 1
function behind_gfw() {
    local return_code=$(curl -I -s --connect-timeout 1 www.google.com -w %{http_code} | tail -n1)
    if [ "${return_code}" = "200" ]; then
        return 1
    fi
    return 0
}

function check_region(){
  if [ "${REGION}" == "" ]; then
    if behind_gfw; then
        REGION=china       # mainland china is behind GFW
        BASEURL="http://download.pigsty.cc/${VERSION}"
    else
        REGION=default     # otherwise use default mirror
    fi
    DEFAULT_SRC_URL="${BASEURL}/${SRC_FILENAME}"
    DEFAULT_PKG_URL="${BASEURL}/${PKG_FILENAME}"
    log_info "region = ${REGION}"
  fi
}

#----------------------------------------------#
# kernel
#----------------------------------------------#
function check_kernel(){
    local kernel_name=$(uname -s)
    if [[ "${kernel_name}" == "Linux" ]]; then
        log_info "kernel = ${kernel_name}"
        return 0
    else
        log_error "kernel = ${kernel_name}, not supported, Linux only"
        exit 1
    fi
}

#----------------------------------------------#
# machine
#----------------------------------------------#
function check_machine(){
    local machine_name=$(uname -m)
    if [[ "${machine_name}" == "x86_64" ]]; then
        log_info "machine = ${machine_name}"
        return 0
    else
        log_error "machine = ${machine_name}, not supported, x86_64 only"
        exit 2
    fi
}

#----------------------------------------------#
# os release (Linux|Darwin etc..)
#----------------------------------------------#
function check_release(){
    if [[ ! -f /etc/redhat-release ]]; then
        log_error "release = unknown, /etc/redhat-release not exists"
        exit 3
    fi
    local full=`cat /etc/redhat-release | tr -dc '0-9.'`
    local major=$(cat /etc/redhat-release | tr -dc '0-9.'|cut -d \. -f1)
    local minor=$(cat /etc/redhat-release | tr -dc '0-9.'|cut -d \. -f2)
    local asynchronous=$(cat /etc/redhat-release | tr -dc '0-9.'|cut -d \. -f3)
    if [[ ${major} != "7" && ${major} != "8" && ${major} != "9"  ]]; then
        log_error "release = ${full} ,  supported release: el7,el8,el9"
        exit 4
    fi
    log_info "release = ${full}"
    return 0
}

#----------------------------------------------#
# sudo
#----------------------------------------------#
function can_nopass_sudo(){
    local current_user=$(whoami)
    if [[ "${current_user}" == "root" ]]; then
        return 0
    fi
    if sudo -n ls >/dev/null 2>/dev/null; then
        return 0
    fi
    return 1
}

function check_sudo(){
    local current_user=$(whoami)
    if can_nopass_sudo; then
        log_info "sudo = ${current_user} ok"
    else
        log_error "sudo = ${current_user} missing nopasswd"
        log_warn "fix nopass sudo for '${current_user}' with sudo:"
        log_hint "echo '%%${current_user} ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/${current_user}"
        exit 5
    fi
}



#----------------------------------------------#
# check pkg.tgz exists (optionally download)
#----------------------------------------------#
function check_pkg(){
    local pkg_url=${1-${DEFAULT_PKG_URL}}    # default package url from github
    local pkg_path=${2-${PKG_PATH}}          # default download path : /tmp/pkg.tgz
    local download_pkg=${3-${DOWNLOAD_PKG}}  # flag: if set, skip interactive asking

    if [[ -f ${pkg_path} ]]; then
        # TODO: checksum validate (but we will use 400MB sanity size check instead)
        if (($(stat -c%s ${pkg_path})>409600000)); then
            log_info "cache = ${pkg_path} exists"
            return 0
        else
            log_warn "cache = ${pkg_path} exists but invalid"
            rm -rf ${pkg_path}
        fi
    fi

    # ask for confirmation if in interactive mode (and download is not specified)
    if [[ ${download_pkg} == "ask" ]]; then
        log_input "Cache /tmp/pkg.tgz not exists, download? (y/n):"
        read -r
        local reply=$(echo "$REPLY" | tr '[:upper:]' '[:lower:]')
        case "${reply}" in
            y|yes|ok|true|aye|on) download_pkg=yes ;;
            n|no|false|nay|off)   download_pkg=no  ;;
        esac
    fi
    if [[ ${download_pkg} == "yes" ]]; then
        log_info "download from Github ${pkg_url} to ${pkg_path}"
        curl -fSL "${pkg_url}" -o "${pkg_path}"
        return $?
    else
        log_warn "cache = missing and skip download"
    fi
    return 0
}


#----------------------------------------------#
# check repo
#----------------------------------------------#
# assume user can sudo (pass check_sudo)
function check_repo(){
    local pkg_path=${1-${PKG_PATH}}         # default download path : /tmp/pkg.tgz
    local repo_dir=${2-${REPO_DIR}}         # default repo directory: /www/pigsty
    local nginx_home=$(dirname ${repo_dir})
    local repo_name=$(basename ${repo_dir})
    if [[ -f ${repo_dir}/repo_complete ]]; then
        log_info "repo = ${repo_dir} ok"
        return 0
    fi
    if [[ ! -f ${pkg_path} ]]; then
        log_warn "repo = skip (${pkg_path} not exists)"
        return 0
    fi
    sudo mkdir -p ${nginx_home}
    if [[ -d ${repo_dir} ]]; then
        log_warn "repo = invalid, remove"
        sudo rm -rf ${repo_dir}
    fi
    log_info "repo = extract from ${pkg_path}"
    sudo tar -xf ${pkg_path} -C ${nginx_home}  # extract to /www/pigsty
}


#----------------------------------------------#
# check local file repo
#----------------------------------------------#
# Usage: add_el7_repo [region] (default|china|europe)
function add_el7_repo(){
    local region=${1-"default"}
    local releasever=7
    local basearch=x86_64
    if [ "${region}" = "china" ]; then
      baseurl="https://mirrors.tuna.tsinghua.edu.cn"
    elif [ "${region}" = "europe" ]; then
      baseurl="https://mirrors.xtom.de"
    else
      baseurl="http://mirror.centos.org"
    fi
    cat > "/tmp/el${releasever}.repo" <<-EOF
[base-default]
name = EL 7 Base \$releasever - \$basearch
baseurl = ${baseurl}/centos/\$releasever/os/\$basearch/
gpgcheck = 0
enabled = 1
skip_if_unavailable = 1
[epel-default]
name = EL 7 EPEL \$releasever - \$basearch
baseurl = ${baseurl}/epel/\$releasever/\$basearch/
gpgcheck = 0
enabled = 1
skip_if_unavailable = 1
[extras-default]
name = EL 7 Extras \$releasever - \$basearch
baseurl = ${baseurl}/centos/\$releasever/extras/\$basearch/
gpgcheck = 0
enabled = 1
skip_if_unavailable = 1
[updates-default]
name = EL 7 Updates \$releasever - \$basearch
baseurl = ${baseurl}/centos/\$releasever/updates/\$basearch/
gpgcheck = 0
enabled = 1
skip_if_unavailable = 1
EOF
      sudo mv -f "/tmp/el${releasever}.repo" "/etc/yum.repos.d/el${releasever}.repo"
}

# Usage: add_el89_repo [region] (default|china|europe)
function add_el89_repo(){
    local region=${1-"default"}
    local suffix=""
    if [ "${region}" = "china" ]; then
      baseurl="https://mirrors.aliyun.com"
      suffix=linux
    elif [ "${region}" = "europe" ]; then
      baseurl="https://mirrors.xtom.de"
    else
      baseurl="https://dl.rockylinux.org/pub"
    fi
    cat > "/tmp/el${releasever}.repo" <<-EOF
[baseos-default]
name = EL ${releasever} BaseOS \$releasever - \$basearch
baseurl = ${baseurl}/rocky${suffix}/\$releasever/BaseOS/\$basearch/os/
gpgcheck = 0
enabled = 1
skip_if_unavailable = 1
module_hotfixes=1
[appstream-default]
name = EL ${releasever} AppStream \$releasever - \$basearch
baseurl = ${baseurl}/rocky${suffix}/\$releasever/AppStream/\$basearch/os/
gpgcheck = 0
enabled = 1
skip_if_unavailable = 1
module_hotfixes=1
[epel-default]
name = EL ${releasever} EPEL \$releasever - \$basearch
baseurl = ${baseurl}/epel/\$releasever/Everything/\$basearch/
gpgcheck = 0
enabled = 1
skip_if_unavailable = 1
module_hotfixes=1
EOF
  sudo mv -f "/tmp/el${releasever}.repo" "/etc/yum.repos.d/el${releasever}.repo"
}


#----------------------------------------------#
# add local file repo
#----------------------------------------------#
function add_local_repo(){
    local nginx_home=${1-${NGINX_HOME}}
    local repo_name=${2-${REPO_NAME}}
    local releasever=$(rpm -q --qf "%{VERSION}" $(rpm -q --whatprovides redhat-release) | grep -o '^[^.]\+')
    cat > /tmp/pigsty-local.repo  <<-EOF
[pigsty-local]
name=pigsty local \$releasever - \$basearch
baseurl=file://${nginx_home}/${repo_name}/
enabled=1
gpgcheck=0
EOF
    if (( ${OS_VERSION} >= 8 )); then
      echo "module_hotfixes=1" >> /tmp/pigsty-local.repo
    fi
    sudo mkdir -p /etc/yum.repos.d/backup
    sudo mv -f /etc/yum.repos.d/*.repo /etc/yum.repos.d/backup/ 2> /dev/null || true
    sudo mv -f /tmp/pigsty-local.repo /etc/yum.repos.d/pigsty-local.repo
}

function check_repo_file(){
    local repo_name=${1-${REPO_NAME}}
    local nginx_home=${2-${NGINX_HOME}}
    local repo_file=/etc/yum.repos.d/${repo_name}-local.repo
    if [[ ! -f ${nginx_home}/${repo_name}/repo_complete ]]; then
        if [[ $OS_VERSION == "7" ]]; then
            add_el7_repo ${REGION}
        elif [[ $OS_VERSION == "8" ||  $OS_VERSION == "9" ]]; then
            add_el89_repo ${REGION}
        fi
        log_warn "repo file = add el${OS_VERSION}.${ARCH} upstream"
        return 0
    else
        add_local_repo
        log_info "repo file = use ${repo_file}"
    fi
    sudo yum clean all -q
    sudo yum makecache -q
    log_info "repo cache = created"
}

#----------------------------------------------#
# check utils
#----------------------------------------------#
# install ansible sshpass unzip wget yum , etc...
function check_utils(){
    local repo_name=${1-${REPO_NAME}}
    local nginx_home=${2-${NGINX_HOME}}
    local repo_file=/etc/yum.repos.d/${repo_name}-local.repo

    if [[ $OS_VERSION == "7" ]]; then
        log_info "install el7 utils"
        sudo yum install -y createrepo_c unzip wget yum-utils createrepo_c sshpass
        sudo yum install -y ansible
    elif [[ $OS_VERSION == "8" ]]; then
        log_info "install el8 utils"
        sudo dnf install -y createrepo_c unzip wget yum-utils createrepo_c sshpass modulemd-tools
        sudo dnf install -y ansible python39-jmespath
    elif [[ $OS_VERSION == "9" ]]; then
        log_info "install el9 utils"
        sudo dnf install -y createrepo_c unzip wget yum-utils createrepo_c sshpass modulemd-tools
        sudo dnf install -y ansible python3-jmespath
    fi
    # check ansible is installed
    if command -v ansible-playbook >/dev/null ; then
        log_info "ansible = $(ansible --version | head -n1)"
    else
        log_error "ansible = not found"
        exit 20
    fi
}


#--------------------------------------------------------------#
# Main
#--------------------------------------------------------------#
function main(){
    # arg parsing
    while [ $# -gt 0 ]; do
        case $1 in
            -h|--help)
                echo './bootstrap [-r|--region <region>] [-p|--path <pkg>] [-y|--yes] [-n|--no]'
                exit 0;;
            -r|--region) REGION="$2"   ; shift ;;
            -p|--path|--pkg) PKG_PATH="$2" ; shift ;;
            -y|--yes) DOWNLOAD_PKG=yes  ;;
            -n|--no)  DOWNLOAD_PKG=no   ;;
            (--) shift; break;;
            (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
            (*) break;;
        esac
        shift
    done

    log_hint "bootstrap pigsty ${VERSION} begin\n"
    check_region     # region        = default
    check_kernel     # kernel        = Linux
    check_machine    # machine       = x86_64
    check_release    # release       = CentOS 7|8|9
    check_sudo       # current_user  = NOPASSWD sudo
    check_pkg        # check offline installation package exists (INTERACTIVE: ask for download confirmation)
    check_repo       # create repo from pkg.tgz if exists
    check_repo_file  # create local file repo file if repo exists
    check_utils      # check ansible sshpass and other utils installed
    log_info "boostrap pigsty complete"
    log_hint "proceed with ./configure\n"
}

main $@

